package io.light.frame.dal.mybatis.generator.core.meta.visit;

import io.light.frame.dal.mybatis.generator.core.cfg.GeneratorConfiguration;
import io.light.frame.dal.mybatis.generator.core.cfg.ModuleConfiguration;
import io.light.frame.dal.mybatis.generator.core.context.GenerationContext;
import io.light.frame.dal.mybatis.generator.core.exceptions.InitializationException;
import io.light.frame.dal.mybatis.generator.core.exceptions.MetaAccessException;
import io.light.frame.dal.mybatis.generator.core.meta.Dialect;
import io.light.frame.dal.mybatis.generator.core.meta.SupportedDialect;
import io.light.frame.dal.mybatis.generator.core.meta.visit.opt.MetaOperations;
import io.light.frame.dal.mybatis.generator.core.util.GenToolKit;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import javax.sql.DataSource;
import java.util.*;

/**
 * Metadata accessor
 *
 * @author Ivan
 * @version 1.0.0
 * @date 2021-05-24 05:49
 */
public class MetaAccessorImpl implements MetaAccessor {

    private final Map<String, DialectJdbcTemplate> jdbcTemplates = new HashMap<>();

    private final GeneratorConfiguration configuration;

    private String defaultDsBeanId;

    public MetaAccessorImpl(GeneratorConfiguration configuration) {
        this.configuration = configuration;
    }

    @Override
    public DialectJdbcTemplate lookupJdbcTemplate() {
        return lookupJdbcTemplate(null);
    }

    private DialectJdbcTemplate lookupJdbcTemplate(String datasourceId) {
        if (StringUtils.isBlank(datasourceId)) {
            GenerationContext context = GenerationContext.current(false);
            if (context != null) {
                datasourceId = context.getConfig().getDatasourceBeanId();
            }
            if (StringUtils.isBlank(datasourceId)) {
                datasourceId = defaultDsBeanId;
            }
        }
        DialectJdbcTemplate jdbcTemplate = jdbcTemplates.get(datasourceId);
        if (jdbcTemplate == null) {
            throw new MetaAccessException("Missing defined JDBC template");
        }
        return jdbcTemplate;
    }

    /**
     * Do meta operations
     *
     * @param datasourceId datasource bean id
     * @param pipe         meta operations pipeline
     * @throws Exception operations error
     */
    @Override
    public <T>T touch(String datasourceId, Pipe<MetaOperations> pipe) throws Exception {
        Dialect dialect = lookupJdbcTemplate(datasourceId).getDialect();
        MetaOperations operation = dialect.getMetaOperations();
        if (operation == null) {
            throw new MetaAccessException(String.format("Unsupported sql dialect '%s'", dialect));
        }
        return (T) pipe.accept(operation);
    }

    private DataSource lookupDefaultDataSource() {
        DataSource defaultDs = null;
        try {
            defaultDs = GenToolKit.beanFactory().getBean(DataSource.class);
        } catch (Exception e) {
            //skip
        }
        if (defaultDs == null) {
            try {
                defaultDs = GenToolKit.beanFactory().getBean("dataSource", DataSource.class);
            } catch (Exception e) {
                //skip
            }
        }
        return defaultDs;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, DataSource> dataSourceMap = GenToolKit.beanFactory().getBeansOfType(DataSource.class);
        if (CollectionUtils.isEmpty(dataSourceMap)) {
            throw new InitializationException("No qualifying datasource bean available");
        }
        DataSource defaultDs = lookupDefaultDataSource();
        defaultDsBeanId = dataSourceMap.entrySet().stream()
                .filter(entry -> entry.getValue() != null && entry.getValue() == defaultDs)
                .map(Map.Entry::getKey)
                .filter(Objects::nonNull)
                .findFirst().orElse(null);
        List<ModuleConfiguration> gens = new ArrayList<>();
        gens.add(configuration);
        gens.addAll(configuration.getOtherModules().values());
        Map<String, Dialect> dialectMap = new HashMap<>(dataSourceMap.size());
        for (ModuleConfiguration cfg : gens) {
            String dsBeanId = cfg.getDatasourceBeanId();
            if (StringUtils.isBlank(dsBeanId)) {
                dsBeanId = defaultDsBeanId;
                cfg.setDatasourceBeanId(dsBeanId);
            }
            if (StringUtils.isBlank(dsBeanId)) {
                throw new MetaAccessException("Default datasource bean not found");
            }
            Dialect dialect = SupportedDialect.of(dataSourceMap.get(dsBeanId));
            if (dialect == null) {
                throw new MetaAccessException(String.format(
                        "The dialect of datasource '%s' is not recognized", dsBeanId));
            }
            Dialect existDialect = dialectMap.putIfAbsent(dsBeanId, dialect);
            if (existDialect != null && existDialect != dialect) {
                throw new MetaAccessException(String.format("Inconsistent dialect settings for datasource bean: '%s'",
                        dsBeanId));
            }
        }
        if (defaultDs != null) {
            jdbcTemplates.put(defaultDsBeanId, new DialectJdbcTemplate(defaultDsBeanId, defaultDs, dialectMap.get(
                    defaultDsBeanId)));
        }
        dataSourceMap.entrySet().stream().filter(entry -> Objects.nonNull(entry.getValue()))
                .filter(entry -> entry.getValue() != defaultDs)
                .forEach(entry -> {
                    jdbcTemplates.put(entry.getKey(), new DialectJdbcTemplate(entry.getKey(), entry.getValue(),
                            dialectMap.get(entry.getKey())));
                });
    }
}
